Skip to content

feat(examples): add CodeBuddy Code CLI integration example(refs #644 ) - #996

Open
pei-pei45 wants to merge 12 commits into
TencentCloud:masterfrom
pei-pei45:feat/codebuddy-sandbox-integration
Open

feat(examples): add CodeBuddy Code CLI integration example(refs #644 )#996
pei-pei45 wants to merge 12 commits into
TencentCloud:masterfrom
pei-pei45:feat/codebuddy-sandbox-integration

Conversation

@pei-pei45

@pei-pei45 pei-pei45 commented Jul 16, 2026

Copy link
Copy Markdown

feat(examples): add CodeBuddy Code CLI integration example(refs #644

CodeBuddy Sandbox Integration

Summary

Add a complete sandbox execution backend that lets CodeBuddy run untrusted code in isolated CubeSandbox MicroVMs. The architecture follows the "keep the LLM agent on the host, route dangerous operations into a disposable VM" pattern.

What's Changed

sandbox_exec.py — Host-side CLI executor

python sandbox_exec.py --code "print(1+1)"
python sandbox_exec.py --file ./script.py
python sandbox_exec.py --cmd "ls -la /workspace"
python sandbox_exec.py --pip requests --code "import requests; print(requests.__version__)"
python sandbox_exec.py --keep-alive --code "state = 42"
  • --code/--file/--cmd/--pip for Python, file execution, and shell commands
  • Cross-process sandbox reuse via UID-scoped session file (/tmp/cubesandbox_codebuddy_session_<uid>, 0600, O_NOFOLLOW)
  • Path validation: only reads from allowed directories (default: cwd), rejects symlinks
  • Command length capped at 64 KB to prevent resource exhaustion
  • Thread-safe via threading.Lock

mcp_server.py — MCP server (JSON-RPC over stdio)

Five tools exposed:

Tool Purpose
sandbox_run_code Run a Python snippet in the sandbox
sandbox_run_command Run an arbitrary shell command
sandbox_write_file Write a file into the sandbox
sandbox_read_file Read a file from the sandbox
sandbox_reset Destroy the cached sandbox
  • Path validation using strict prefix matching (/workspace, /tmp, /home/user)
  • Bounds checking on timeout (max 300 s) and content sizes (code: 100 KB, content: 1 MB)
  • Sanitized error messages — no internal paths or exception details leaked

hooks/ — CodeBuddy bash-routing plugin

# Install
cd hooks && ./install.sh

# Uninstall
./install.sh --uninstall
  • cubesandbox-sandbox.js intercepts the bash tool and routes it through sandbox_exec.py
  • install.sh copies the plugin to ~/.config/codebuddy/plugins/ and merges only allow-listed CUBE_* keys into the CodeBuddy config (provider API keys are never copied)

tests/ — pytest suite (166 tests, fully offline)

  • test_sandbox_exec.py — exec API, sandbox lifecycle, path validation, symlink rejection
  • test_mcp_server.py — request handling, tool calls, validation, error paths
  • test_codebuddy_common.py — helpers, stream writer, command execution
  • test_env_utils.py — pre-existing env utility tests

Security

Protection Implementation
Path traversal Allowlist prefix matching + os.path.realpath resolution
Symlink attacks Reject any path component that is a symlink
TOCTOU races O_NOFOLLOW + O_EXCL on session file
Information disclosure Error messages truncated, no internal paths
Resource exhaustion Command length cap (64 KB), timeout bounds (5 min)
Thread safety threading.Lock on sandbox access

Testing

cd examples/codebuddy-integration
pip install pytest
pytest tests/ -v
# 166 passed

Assisted-by: Cursor:composer-2.5-fast
Signed-off-by: YanxuanLiu 3205348955@qq.com
屏幕截图 2026-07-16 224209
屏幕截图 2026-07-16 224228
屏幕截图 2026-07-16 234339
屏幕截图 2026-07-16 234509
屏幕截图 2026-07-16 234516

Comment thread examples/codebuddy-integration/run_codebuddy.py Outdated
Comment thread docs/guide/integrations/codebuddy.md Outdated
Comment thread docs/guide/integrations/codebuddy.md Outdated
@cubesandboxbot

cubesandboxbot Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review: feat(examples): add CodeBuddy Code CLI integration example (PR #996)

This review is AI-generated and does not represent human approval.


Overview

This PR adds a substantial CodeBuddy Code CLI integration example (5,629 additions across 25 files). It includes a Docker image, host-side executor scripts, an MCP server, a CodeBuddy bash-routing plugin with installer, comprehensive documentation in both English and Chinese, a CI workflow, and a 166-test pytest suite. The architecture follows the sound "keep the LLM agent on the host, route dangerous operations into a disposable VM" pattern.

The overall quality is high — the code is well-structured, security-conscious (path validation, symlink rejection, session file hardening, credential separation), thoroughly tested, and well-documented. Below are issues I recommend addressing.


Findings (ranked by severity)

Medium

1. CI workflow leaks container on exec failure (codebuddy-integration.yml:75-86)

The workflow starts a daemon container with --rm, then runs docker exec commands, then docker rm -f "$cid". If any docker exec fails (e.g., codebuddy --version returns non-zero), the shell exits before reaching the cleanup line, leaving the container running. The --rm flag only removes the container after its main process exits, which won't happen for a daemon.

Failure scenario: A transient npm install failure causes codebuddy --version to fail. The CI step aborts without cleanup, and the container keeps running on the runner until the job times out, consuming resources.

Suggested fix: Add a trap at the top of the run: block:

cid=""
cleanup() { [ -n "$cid" ] && docker rm -f "$cid" 2>/dev/null || true; }
trap cleanup EXIT
cid=$(docker run -d --rm codebuddy-cube:ci)

2. mcp_server.py duplicates _codebuddy_common.run_command (mcp_server.py:2563-2584)

The MCP server defines its own run_command() that returns a dict, while _codebuddy_common.run_command() returns an SDK result object. Both execute sandbox.commands.run() with similar error handling (catching CommandExitException). This means any bug fixes or improvements to the shared helper won't apply to the MCP server.

Suggested fix: Refactor the common run_command() to also offer a dict-return variant, or have mcp_server.py call _codebuddy_common.run_command() and convert the result.

3. Package name regex rejects valid PEP 508 names (sandbox_exec.py:3686)

The regex _PACKAGE_NAME_RE is:

r"^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$|^[A-Za-z0-9]$"

PEP 508 allows trailing ._- characters (e.g., foo_), but this regex requires the name to end with an alphanumeric character. The comment says "be conservative," which is a reasonable security stance, but this should be documented more explicitly as an intentional restriction. Users hitting this with valid package names may not understand why they're blocked.

Low

4. Container lifecycle: --rm is redundant with explicit docker rm -f (codebuddy-integration.yml:75,86)

The workflow passes --rm to docker run then explicitly calls docker rm -f "$cid" at the end. --rm only activates on container exit, not on explicit docker rm -f, so the flag has no effect in the happy path. Nonetheless, keeping it is harmless as a belt-and-suspenders for the failure path (even though, as noted in finding 1, the failure path doesn't reach either cleanup).

5. mcp_server.py lacks notifications/cancelled handler (mcp_server.py:2816)

The MCP protocol defines notifications/cancelled which is silently passed to the catch-all error handler (-32601). While the cancellation notification is optional for simple servers, the error response for a notification (which expects no response) is technically a protocol violation. This is a trivial fix — either add a handler that returns None or add it to the notifications/initialized guard.

6. _codebuddy_common.py imports SDK internals despite claiming SDK-agnosticism (_codebuddy_common.py:1540)

The module docstring says it's "SDK-agnostic (duck-typed on sandbox.commands.run)," but line 1540 imports CommandExitException from e2b.sandbox.commands.command_handle. This creates a hard runtime dependency on the e2b SDK internals. The catch in run_command() is pure convenience (swallowing non-zero exits), so this could be replaced with a broad except Exception to preserve SDK agnosticism.

Notes

7. Session file TOCTOU is acknowledged but not eliminated (sandbox_exec.py:3693-3701)

The code includes a detailed comment about the TOCTOU race between FileExistsError unlink() and the retry open(). The three-attempt retry with backoff mitigates but does not eliminate the window. The code also correctly notes that on shared clusters, per-invocation sandboxes (without --keep-alive) bypass this entirely. This is a well-documented and acceptable trade-off.

8. Input validation is thorough and correct

Path traversal prevention (_validate_path, exec_file), symlink rejection (O_NOFOLLOW + S_ISREG), command length caps (64 KB), timeout bounds (5 min), and credential stripping from proxy URLs are all implemented correctly. The execFile usage in the JS plugin avoids shell injection via argument passing.

9. Test coverage is strong

The 166-test suite covers both success paths and failure modes (empty input, oversized input, non-string input, invalid paths, symlink rejection, SDK errors, stream writing edge cases). Tests are fully offline via mocking.


Summary

This is a well-crafted integration example that demonstrates deep understanding of the CubeSandbox security model. The three medium findings (CI cleanup, code duplication, PEP 508 regex) are worth addressing. No blocking issues were found.

@pei-pei45 pei-pei45 changed the title feat(examples): add CodeBuddy Code CLI integration example feat(examples): add CodeBuddy Code CLI integration example(refs #644 ) Jul 18, 2026
.env.example documents CUBE_API_URL / CUBE_API_KEY as the canonical names
with E2B_* as legacy aliases, but several files only read E2B_* and would
silently use defaults when a user configures via .env.example as-is.

Changes:
- env_utils.py: add cube_required(cube_name, legacy_name) helper that checks
  canonical name before legacy name and emits a clear error if neither is set.
- run_codebuddy.py, resume_codebuddy.py, network_policy.py: replace the two
  required("E2B_") calls with cube_required("CUBE_API_URL", "E2B_API_URL")
  and cube_required("CUBE_API_KEY", "E2B_API_KEY").
- .env.example, README.md, README_zh.md: update variable names to canonical.

Existing deployments that only set E2B_* continue to work unchanged.

Assisted-by: AGENT_NAME:claude-opus-4-8
Co-authored-by: Cursor <cursoragent@cursor.com>
@pei-pei45
pei-pei45 force-pushed the feat/codebuddy-sandbox-integration branch from 6ba00e1 to 82657fe Compare July 22, 2026 09:12
Comment thread examples/codebuddy-integration/tests/conftest.py Outdated
Comment thread docs/guide/integrations/index.md Outdated
Comment thread examples/codebuddy-integration/hooks/cubesandbox-sandbox.js Outdated
Comment thread examples/codebuddy-integration/mcp_server.py
Comment thread examples/codebuddy-integration/sandbox_exec.py Outdated
Comment thread examples/codebuddy-integration/sandbox_exec.py
Comment thread examples/codebuddy-integration/env_utils.py Outdated
Comment thread examples/codebuddy-integration/mcp_server.py
Comment thread docs/guide/integrations/index.md Outdated
Comment thread .github/workflows/codebuddy-integration.yml
Comment thread examples/codebuddy-integration/hooks/install.sh
Comment thread examples/codebuddy-integration/env_utils.py Outdated
Comment thread examples/codebuddy-integration/sandbox_exec.py
Comment thread examples/codebuddy-integration/_codebuddy_common.py Outdated
Comment thread .github/workflows/codebuddy-integration.yml Outdated
Comment thread examples/codebuddy-integration/mcp_server.py
Comment thread examples/codebuddy-integration/sandbox_exec.py
Comment thread examples/codebuddy-integration/mcp_server.py
Comment thread examples/codebuddy-integration/sandbox_exec.py Outdated
Comment thread examples/codebuddy-integration/env_utils.py
Comment thread examples/codebuddy-integration/mcp_server.py
Comment thread examples/codebuddy-integration/mcp_server.py
Comment thread examples/codebuddy-integration/README.md
Comment thread examples/codebuddy-integration/sandbox_exec.py
Comment thread examples/codebuddy-integration/sandbox_exec.py Outdated
Comment thread examples/codebuddy-integration/env_utils.py
Comment thread examples/codebuddy-integration/env_utils.py
Comment thread examples/codebuddy-integration/_codebuddy_common.py Outdated
Comment thread examples/codebuddy-integration/mcp_server.py
Comment thread examples/codebuddy-integration/sandbox_exec.py Outdated
Comment thread examples/codebuddy-integration/network_policy.py
Comment thread examples/codebuddy-integration/mcp_server.py
Comment thread examples/codebuddy-integration/tests/test_env_utils.py
Comment thread examples/codebuddy-integration/hooks/install.sh
Comment thread .github/workflows/codebuddy-integration.yml Outdated
Comment thread examples/codebuddy-integration/mcp_server.py
Comment thread examples/codebuddy-integration/tests/test_sandbox_exec.py Outdated
# add a retry loop because envd may still be booting on cold cache.
for i in $(seq 30); do
if docker exec "$cid" curl -fsS -o /dev/null \
http://127.0.0.1:49983/health; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential orphan container on exec failure

If docker exec "$cid" codebuddy --version (or any later docker exec) returns a non-zero exit, the shell exits before reaching docker rm -f "$cid" on line 86. The --rm flag only removes the container after its main process exits, which won't happen for a daemon. Container stays alive until the runner reaps it.

Consider adding a trap at the top of this run: block:

cid=""
cleanup() { [ -n "$cid" ] && docker rm -f "$cid" 2>/dev/null || true; }
trap cleanup EXIT
cid=$(docker run -d --rm codebuddy-cube:ci)

This guarantees cleanup regardless of which command fails.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job runs on GitHub-hosted ubuntu-latest runners, which are fully ephemeral — the VM is destroyed once the job finishes, so an orphaned container here doesn't persist or accumulate across runs. Since this is the only container operation in the static job, I'll leave it as-is for now, but agree the trap pattern would be worth adding if this ever moves to a self-hosted runner or if more container steps are added to this job later.

try:
result = _get_sandbox().commands.run(cmd, timeout=timeout)
return {
"exit_code": result.exit_code,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated run_command — diverges from _codebuddy_common

mcp_server.py defines its own run_command() (returning a dict) while _codebuddy_common.run_command() does essentially the same thing (returning an SDK result object). Any bug fix or improvement to the shared helper will not apply here.

Suggested approach: either refactor _codebuddy_common.run_command() to optionally return a dict, or have this function call the shared helper and convert the result. At minimum leave a docstring cross-reference so future maintainers know both copies exist.

@pei-pei45 pei-pei45 Jul 28, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point on the duplication. Given mcp_server.py needs a dict-shaped return (for JSON-RPC serialization) while _codebuddy_common.run_command() returns an SDK result object, I'll leave them separate for now but will add a docstring cross-reference noting both copies exist, so future maintainers aren't surprised.


# PEP 508 package name validator — compiled once at module load.
_PACKAGE_NAME_RE = re.compile(r"^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$|^[A-Za-z0-9]$")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package name regex rejects valid PEP 508 names

PEP 508 allows trailing ._- characters (e.g., foo_), but this regex requires the name to end with [A-Za-z0-9]. The comment says "be conservative" which is a reasonable security choice, but users hitting this with legitimate package names may not understand why they're blocked.

Recommend either expanding the regex to match the full PEP 508 spec, or adding an explicit comment noting this is a deliberate restriction (not a bug) so future maintainers don't "fix" it without understanding the security rationale.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional — the conservative pattern is a deliberate security choice to reject ambiguous package names rather than a bug. Will add an explicit comment above the regex noting this so it doesn't get "fixed" by a future contributor without context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants